

local kong = kong
local ngx = ngx
local string = string
local time = ngx.time
local null = ngx.null
local json = require "cjson"
local kong_shared = ngx.shared.kong

local env = require "kong.cmd.utils.env"
local log = require "kong.cmd.utils.log"
local singletons = require "kong.singletons"
local constants = require "kong.constants"
local uuid = require "kong.tools.utils".uuid

local plugin_name = ({...})[1]:match("^kong%.plugins%.([^%.]+)")
-- local http_sender = require "kong.plugins.plugin-manage.sender.http_sender"
local http_sender = require("kong.plugins." .. plugin_name .. ".sender.http_sender")

local DEFAULT_PLUGINS_PATH = "/opt/kong/kong/kong/plugins/"
local DEFAULT_CONF_PATH = "/etc/kong/"
local DEFAULT_CONF_NAME = "kong.conf"
local DEFAULT_POD_CLUSTER = "NO_CLUSTER_INFO"
local DEFAULT_UPDATE_SEEN_INTERVAL = 5

--local INSERT_POD_INFO_SQL = "REPLACE INTO cluster_pods(`%s`,`%s`,`%s`,`%s`,`%s`,`%s`,`%s`) VALUES ('%s',FROM_UNIXTIME(%s),FROM_UNIXTIME(%s),FROM_UNIXTIME(%s),'%s','%s','%s');"
local INSERT_POD_INFO_SQL = [[
INSERT INTO cluster_pods (
  `id`,
  `pod_start_time`,
  `pod_last_seen`,
  `pod_last_reload`,
  `pod_hostname`,
  `pod_ip`,
  `pod_plugins`,
  `cluster_id`,
  `pod_cluster`,
  `pod_config_version`)
VALUES (
  '%s',    -- pod_id
  now(),   -- pod_start_time
  now(),   -- pod_last_seen
  now(),   -- pod_last_reload
  '%s',    -- pod_hostname
  '%s',    -- pod_ip
  '%s',    -- pod_plugins
  '%s',    -- cluster_id
  '%s',    -- pod_cluster
  '%s')    -- pod_config_version
ON DUPLICATE KEY UPDATE
  `pod_last_reload` = now(),
  `pod_last_seen` = now(),
  `pod_ip` = '%s',
  `pod_plugins` = '%s',
  `cluster_id` = '%s',
  `pod_cluster` = '%s',
  `pod_config_version` = '%s';
]]

local UPDATE_POD_INFO_SQL = [[
UPDATE cluster_pods
SET `%s` = %s
WHERE `pod_hostname` = '%s';
]]

local SELECT_CLUSTER_LIST_SQL = [[
SELECT id, cluster_name, master_pod_name, master_pod_ip, cluster_url
FROM clusters;
]]


local UPDATE_VERSION_SQL = [[
INSERT INTO parameters (
  `key`,
  `value`,
  `created_at`)
VALUES (
  'config_version',
  '%s',
  now())
ON DUPLICATE KEY UPDATE
  `value` = '%s',
  `created_at` = now();
]]

local SELECT_VERSION_SQL = [[
SELECT `key`, `value`, `created_at`
FROM parameters
WHERE `key`='config_version';
]]

local SELECT_MASTER_POD_SQL = [[
SELECT `id`, `cluster_name`, `master_pod_name`,`master_pod_ip`
FROM clusters
WHERE `cluster_name`  ='%s';
]]

local UPDATE_MASTER_POD_SQL = [[
INSERT INTO clusters (
  `id`,
  `cluster_name`,
  `cluster_url`,
  `master_pod_name`,
  `master_pod_ip`)
VALUES (
  '%s',
  '%s',
  '%s',
  '%s',
  '%s')
ON DUPLICATE KEY UPDATE
  `cluster_url` = '%s',
  `master_pod_name` = '%s',
  `master_pod_ip`   = '%s';
]]

local DELETE_EXPIRED_NODES_SQL = [[
DELETE FROM cluster_pods
WHERE TIMESTAMPDIFF(SECOND, pod_last_seen, current_timestamp()) > %s;
]]


--- 查询环境变量
--@param env_name 环境变量名
local function get_env(env_name)
  local env_name = env_name
  if not env_name then
    local err = "环境变量名为空。"
    return nil, err
  end

  if not env then
    local err = "系统环境变量查询失败。"
    return nil, err
  end

  local env_list = env.read_all()

  if not env_list then
    local err = "系统环境变量列表为空。"
    return nil, err
  end

  if not env_list[env_name] then
  local err = "环境变量未找到。"
  return nil, err
  end

  return env_list[env_name]
end


--- 插件初始化
do
  local ok = get_env("KONG_UPDATE_SEEN_INTERVAL")
  if not ok then
    log.error("插件管理：环境变量\"KONG_UPDATE_SEEN_INTERVAL\"未配置")
  end
  local ok = get_env("KONG_PLUGINS_PATH")
  if not ok then
    log.error("插件管理：环境变量\"KONG_PLUGINS_PATH\"未配置")
  end
  local ok = get_env("KONG_CONF_PATH")
  if not ok then
    log.error("插件管理：环境变量\"KONG_CONF_PATH\"未配置")
  end
  local ok = get_env("KONG_CONF_NAME")
  if not ok then
    log.error("插件管理：环境变量\"KONG_CONF_NAME\"未配置")
  end
  local ok = get_env("KONG_POD_CLUSTER")
  if not ok then
    log.error("插件管理：环境变量\"KONG_POD_CLUSTER\"未配置")
  end
  local ok = get_env("KONG_CLUSTER_URL")
  if not ok then
    log.error("插件管理：环境变量\"KONG_CLUSTER_URL\"未配置")
  end
  local ok = get_env("KONG_CLUSTER_ID")
  if not ok then
    log.error("插件管理：环境变量\"KONG_CLUSTER_ID\"未配置")
  end
end


--- 查询环境变量列表
local function get_env_list()
  if not env then
    local err = "系统环境变量查询失败。"
    return nil, err
  end

  local env_list = env.read_all()

  return env_list
end


--- 查询集群列表
local function get_cluster_list()
  local sql = SELECT_CLUSTER_LIST_SQL

  local db = kong.db
  local connector = db.connector
  local res, err = connector:query(sql)
  if not res then
    local err = "查询集群信息失败：" .. err
    kong.log.warn(err)
    return nil, err
  end

  local list = {}
  for _, v in pairs(res) do
    list[v.cluster_name] = v
  end
  return list
end


--- 查询last_seen更新间隔
local function get_update_seen_interval()
  local update_seen_interval = get_env("KONG_UPDATE_SEEN_INTERVAL")
  if not update_seen_interval then
    update_seen_interval = DEFAULT_UPDATE_SEEN_INTERVAL
  end
  return update_seen_interval
end


--- 查询插件存储路径
local function get_default_plugins_path()
  local kong_plugins_path, _ = kong_shared:get("KONG_PLUGINS_PATH")
  if kong_plugins_path then
    return kong_plugins_path
  end

  local plugins_path_env = get_env("KONG_PLUGINS_PATH")
  if plugins_path_env then
    kong_shared:set("KONG_PLUGINS_PATH", plugins_path_env)
    return plugins_path_env

  else
    kong_shared:set("KONG_PLUGINS_PATH", plugins_path_env)
    return DEFAULT_PLUGINS_PATH
  end
end


--- 查询配置文件路径
local function get_default_conf_path()
  local kong_conf_path, _ = kong_shared:get("KONG_CONF_PATH")
  if kong_conf_path then
    return kong_conf_path
  end

  local conf_path_env = get_env("KONG_CONF_PATH")
  if conf_path_env then
    kong_shared:set("KONG_CONF_PATH", conf_path_env)
    return conf_path_env

  else
    kong_shared:set("KONG_CONF_PATH", DEFAULT_CONF_PATH)
    return DEFAULT_CONF_PATH
  end
end


--- 查询配置文件名
local function get_default_conf_name()
  local kong_conf_name, _ = kong_shared:get("KONG_CONF_NAME")
  if kong_conf_name then
    return kong_conf_name
  end

  local conf_name_env = get_env("KONG_CONF_NAME")
  if conf_name_env then
    kong_shared:set("KONG_CONF_NAME", conf_name_env)
    return conf_name_env

  else
    kong_shared:set("KONG_CONF_NAME", DEFAULT_CONF_NAME)
    return DEFAULT_CONF_NAME
  end
end


--- 查询节点集群
local function get_pod_cluster()
  local pod_cluster_env = get_env("KONG_POD_CLUSTER")
  if pod_cluster_env then
    return pod_cluster_env
  else
    return DEFAULT_POD_CLUSTER
  end
end


--- 查询集群URL
local function get_cluster_url()
  local kong_plugins_path = get_env("KONG_CLUSTER_URL")
  if kong_plugins_path then
    return kong_plugins_path
  else
    kong.log.err("获取集群URL失败，请检查环境变量 KONG_CLUSTER_URL 是否已配置")
    return "NULL"
  end
end


--- 获取节点主机名
local function get_pod_hostname()
  local f = io.popen("/bin/hostname")
  local hostname = f:read("*a") or ""
  f:close()
  return string.gsub(hostname, "\n$", "")
end


--- 获取节点IP
local function get_pod_ip()
  local f = io.popen("/bin/hostname -i")
  local hostname = f:read("*a") or ""
  f:close()
  hostname = string.gsub(hostname, "\n$", "")
  local ips = {}
  for ip in hostname:gmatch("([^ ]*)") do
    table.insert(ips, ip)
  end
  local pod_ip = "~"
  if #ips > 0 then
    pod_ip = ips[1]
  end
  return pod_ip
end


--- 获取配置版本与配置时间
local function get_config_version()
  local sql = SELECT_VERSION_SQL

  local db = kong.db
  local connector = db.connector
  local res, _ = connector:query(sql)

  if not res or #res < 1  then
    local msg = "查询配置版本失败。"
    kong.log.warn(msg)
    return nil, msg
  end

  return res[1].created_at
end


--- 更新配置版本与配置时间
local function update_config_version()
  local config_version = uuid()

  local sql = string.format(UPDATE_VERSION_SQL,config_version,config_version)

  local db = kong.db
  local connector = db.connector
  local res, _ = connector:query(sql)

  if not res or res.affected_rows < 1 then
    local msg = "更新配置版本失败。"
    kong.log.warn(msg)
    return nil, msg
  end

  local msg = "更新配置版本成功。"
  return true, msg
end


--- 获取集群数据库id
local function get_cluster_id()
  local res
  if get_env("KONG_CLUSTER_ID") then
    res = get_env("KONG_CLUSTER_ID")
  else
    res = kong.cluster.get_id()
  end

  return res
end


--- 校验字符串是否为空
-- @param str 待校验字符串
local function is_present(str)
  return str and type(str) == "string" and str ~= "" and #str > 0 and str ~= null
end


--- 查询master节点
local function get_master_pod(pod_cluster)
  local pod_cluster = pod_cluster
  local sql = string.format(SELECT_MASTER_POD_SQL,pod_cluster)

  local db = kong.db
  local connector = db.connector
  local res, err = connector:query(sql)
  if not res or type(res) ~= "table" then
    local err = "查询master节点失败：" .. err
    kong.log.warn(err)
    return nil, err
  end

  if #res == 1 then
    kong_shared:set("kunlun_cluster_master_pod", res[1])
    return res[1]
  else
    return nil
  end
end


--- 成为master节点
local function become_master_pod(pod_cluster, pod_hostname, pod_ip)
  local id = uuid()
  local pod_cluster = pod_cluster
  local pod_hostname = pod_hostname
  local pod_ip = pod_ip
  local cluster_url = get_cluster_url()
  local sql = string.format(UPDATE_MASTER_POD_SQL,
    id,pod_cluster,cluster_url,pod_hostname,pod_ip,
    cluster_url,pod_hostname,pod_ip
  )

  local db = kong.db
  local connector = db.connector
  local res, err = connector:query(sql)
  if not res or type(res) ~= "table" then
    local err = "成为master节点失败,数据库写入失败：" .. err
    kong.log.warn(err)
  end

  local master_pod = get_master_pod(pod_cluster)
  kong_shared:set("kunlun_cluster_master_pod", master_pod)

  if master_pod.master_pod_name ~= pod_hostname then
    kong.log.warn("成为master节点失败：当前master节点为" .. master_pod.master_pod_name)
    return nil, master_pod

  else
    kong.log.warn("成为master节点成功：当前master节点为" .. master_pod.master_pod_name)
    return true, master_pod
  end

end

--- 校验文件是否存在
-- @param absolute_path 待校验文件完整路径
local function file_exists(absolute_path)

  -- 尝试以只读模式打开文件
  local file = io.open(absolute_path, "r")

  -- 成功打开则关闭文件流
  if file then
    file:close()
  end

  return file ~= nil
end


--- 清理过期节点
local function clean_up_expired_nodes()
  local update_seen_interval = get_update_seen_interval()
  local timeout_duration = 3 * update_seen_interval

  local sql = string.format(DELETE_EXPIRED_NODES_SQL ,timeout_duration)

  local db = kong.db
  local connector = db.connector
  local res, err = connector:query(sql)

  if not res then
    local msg = "数据库删除失败," .. err
    return nil, msg
  elseif res.affected_rows >= 1 then
    local msg = "清理过期节点" .. res.affected_rows .. "个。"
    return nil, msg
  else
    local msg = "无过期节点。"
    return true, msg
  end
end


return {
  get_env                  = get_env,
  get_env_list             = get_env_list,
  get_update_seen_interval = get_update_seen_interval,
  get_default_plugins_path = get_default_plugins_path,
  get_default_conf_path    = get_default_conf_path,
  get_default_conf_name    = get_default_conf_name,
  get_pod_cluster          = get_pod_cluster,
  get_cluster_url          = get_cluster_url,
  get_pod_hostname         = get_pod_hostname,
  get_pod_ip               = get_pod_ip,
  get_config_version       = get_config_version,
  update_config_version    = update_config_version,
  get_cluster_id           = get_cluster_id,
  is_present               = is_present,
  file_exists              = file_exists,
  get_master_pod           = get_master_pod,
  become_master_pod        = become_master_pod,
  get_cluster_list         = get_cluster_list,
  clean_up_expired_nodes   = clean_up_expired_nodes,
}
